/*
 * $Id: InetAddress.cc,v 1.27 2006-02-13 16:04:10 bacon Exp $
 */

#include <xpp/net/InetAddress.h>
#include <xpp/net/Socket.h>
#include <xp/bas/memory.h>
#include <xp/bas/locale.h>
#include <xp/bas/assert.h>
#include <xp/bas/endian.h>
#include <xp/bas/string.h>

namespace xpp
{
	namespace net
	{

		using namespace xpp::bas;

		InetAddress::InetAddress ()
		{
			xp_memset (&address_storage, 0, sizeof(address_storage));
			address_storage.ss_family = (Socket::v6_supported == -1)? V4: V0;
			address_type = 0;
		}

		InetAddress::InetAddress (int family, int type) 
		{
			xp_memset (&address_storage, 0, sizeof(address_storage));
			address_storage.ss_family = (family == V0)?  
				((Socket::v6_supported == -1)? V4: V0): family;
			address_type = type;
		}

		InetAddress::InetAddress (const InetAddress& adr)
		{
			xp_memcpy (&address_storage,
				&adr.address_storage, xp_sizeof(address_storage));
			address_type = adr.address_type;
		}

		InetAddress::InetAddress (const struct sockaddr_storage& adr)
		{
			xp_memcpy (&address_storage, &adr, xp_sizeof(address_storage));
			address_type = 0;
		}

		InetAddress::InetAddress (const struct sockaddr_in& adr)
		{
			xp_assert (xp_sizeof(address_storage) >= xp_sizeof(adr));
			xp_memcpy (&address_storage, &adr, xp_sizeof(adr));
			address_type = 0;
		}

		InetAddress::InetAddress (const struct sockaddr_in6& adr)
		{
			xp_assert (xp_sizeof(address_storage) >= xp_sizeof(adr));
			xp_memcpy (&address_storage, &adr, xp_sizeof(adr));
			address_type = 0;
		}

		InetAddress& InetAddress::operator= (const InetAddress& adr)
		{
			xp_memcpy (&address_storage, 
				&adr.address_storage, xp_sizeof(address_storage));
			address_type = adr.address_type;
			return *this;
		}

		InetAddress& InetAddress::operator= (const struct sockaddr_storage& adr)
		{
			xp_memcpy (&address_storage, &adr, xp_sizeof(address_storage));
			address_type = 0;
			return *this;
		}

		InetAddress& InetAddress::operator= (const struct sockaddr_in& adr)
		{
			xp_assert (xp_sizeof(address_storage) >= xp_sizeof(adr));
			xp_memcpy (&address_storage, &adr, xp_sizeof(adr));
			address_type = 0;
			return *this;
		}

		InetAddress& InetAddress::operator= (const struct sockaddr_in6& adr)
		{
			xp_assert (xp_sizeof(address_storage) >= xp_sizeof(adr));
			xp_memcpy (&address_storage, &adr, xp_sizeof(adr));
			address_type = 0;
			return *this;
		}

		// NOTE: operator== and operator!= do not consider address_type
		//       in equality testing.

		bool InetAddress::operator== (const InetAddress& adr) const
		{
			//xp_size_t ss = this->storageSize();
			//if (ss != adr.storageSize()) return false;
			//return xp_memcmp (this->storage(), adr.storage(), ss) == 0;
			return hasEqualFamily(adr) && 
				hasEqualHost(adr) && hasEqualService(adr);
		}

		bool InetAddress::operator!= (const InetAddress& adr) const
		{
			//xp_size_t ss = this->storageSize();
			//if (ss != adr.storageSize()) return true;
			//xp_memcmp(this->storage(), adr.storage(), ss));
			//return xp_memcmp(this->storage(), adr.storage(), ss) != 0;
			return !hasEqualFamily(adr) ||
				!hasEqualHost(adr) || !hasEqualService(adr);
		}

		bool InetAddress::hasEqualFamily (const InetAddress& addr) const
		{
			return address_storage.ss_family == addr.address_storage.ss_family;
		}

		bool InetAddress::hasEqualHost (const InetAddress& addr) const
		{
			if (address_storage.ss_family == V4) {
				if (addr.address_storage.ss_family != V4) return false;
			
				struct sockaddr_in* p1 = 
					(struct sockaddr_in*)&address_storage;
				struct sockaddr_in* p2 = 
					(struct sockaddr_in*)&addr.address_storage;

				return xp_memcmp(&p1->sin_addr, 
					&p2->sin_addr, xp_sizeof(p1->sin_addr)) == 0;
			}

			if (address_storage.ss_family == V6) {
				if (addr.address_storage.ss_family != V6) return false;

				struct sockaddr_in6* p1 = 
					(struct sockaddr_in6*)&address_storage;
				struct sockaddr_in6* p2 = 
					(struct sockaddr_in6*)&addr.address_storage;

				return xp_memcmp(&p1->sin6_addr, 
					&p2->sin6_addr, xp_sizeof(p1->sin6_addr)) == 0;
			}

			return false;
		}

		bool InetAddress::hasEqualService (const InetAddress& addr) const
		{
			if (address_storage.ss_family == V4) {
				if (addr.address_storage.ss_family != V4) return false;
			
				struct sockaddr_in* p1 = 
					(struct sockaddr_in*)&address_storage;
				struct sockaddr_in* p2 = 
					(struct sockaddr_in*)&addr.address_storage;

				return xp_memcmp(&p1->sin_port, 
					&p2->sin_port, xp_sizeof(p1->sin_port)) == 0;
			}

			if (address_storage.ss_family == V6) {
				if (addr.address_storage.ss_family != V6) return false;

				struct sockaddr_in6* p1 = 
					(struct sockaddr_in6*)&address_storage;
				struct sockaddr_in6* p2 = 
					(struct sockaddr_in6*)&addr.address_storage;

				return xp_memcmp(&p1->sin6_port, 
					&p2->sin6_port, xp_sizeof(p1->sin6_port)) == 0;
			}

			return false;
		}


		Address::len_t InetAddress::storageSize () const
		{
			if (address_storage.ss_family == V4) {
				xp_assert (
					xp_sizeof(address_storage) >= 
					xp_sizeof(struct sockaddr_in));
				return (len_t)xp_sizeof(struct sockaddr_in);
			}

			if (address_storage.ss_family == V6) {
				xp_assert (
					xp_sizeof(address_storage) >= 
					xp_sizeof(struct sockaddr_in6));
				return (len_t)xp_sizeof(struct sockaddr_in6);
			}

			return (len_t)xp_sizeof(address_storage);
		}

		bool InetAddress::isLoopBack () const 
		{
			if (address_storage.ss_family == V4) {
				struct sockaddr_in* v4 = 
					(struct sockaddr_in*)&address_storage;
				return v4->sin_addr.s_addr == xp_hton32(INADDR_LOOPBACK);
			}
			else if (address_storage.ss_family == V6) {
				struct sockaddr_in6* v6 = 
					(struct sockaddr_in6*)&address_storage;
				return xp_memcmp (&v6->sin6_addr, 
					&in6addr_loopback, sizeof(in6addr_loopback)) == 0;
			}

			return false;
		}

		bool InetAddress::isAnyLocal () const 
		{
			if (address_storage.ss_family == V4) {
				struct sockaddr_in* v4 = 
					(struct sockaddr_in*)&address_storage;
				return v4->sin_addr.s_addr == xp_hton32(INADDR_ANY);
			}
			else if (address_storage.ss_family == V6) {
				struct sockaddr_in6* v6 = 
					(struct sockaddr_in6*)&address_storage;
				return xp_memcmp (&v6->sin6_addr, 
					&in6addr_any, sizeof(in6addr_any)) == 0;
			}

			return false;
		}

		int InetAddress::getNumericHost (xp_char_t* buf, xp_size_t size) const
		{
			xp_sckadr_t* sa = storage();
			xp_scklen_t len = storageSize();
			
		#if !defined(__linux) && !defined(__sun) && !defined(_WIN32)
			sa->sa_len = len;
		#endif

		#if defined(XP_CHAR_IS_MCHAR)
			return (::getnameinfo (sa, len,
				buf, size, XP_NULL, 0, NI_NUMERICHOST) == 0)? 0: -1;
		#else
			xp_mchar_t mb_buf[MAX_HOST_LEN + 1];
			xp_size_t n;
			
			if (::getnameinfo (
				sa, len, mb_buf, xp_countof(mb_buf), 
				XP_NULL, 0, NI_NUMERICHOST) != 0) return -1;
			n = xp_mcstowcs (mb_buf, buf, size);
			if (n == (xp_size_t)-1 || n == 0) return -1;
			return 0;
		#endif
		}

		int InetAddress::getNumericService (
			xp_char_t* buf, xp_size_t size) const
		{
			xp_sckadr_t* sa = storage();
			xp_scklen_t len = storageSize();
			
		#if !defined(__linux) && !defined(__sun) && !defined(_WIN32)
			sa->sa_len = len;
		#endif

		#if defined(XP_CHAR_IS_MCHAR)
			return (::getnameinfo (sa, len,
				XP_NULL, 0, buf, size, NI_NUMERICSERV) == 0)? 0: -1;
		#else
			xp_mchar_t mb_buf[MAX_SERVICE_LEN + 1];
			xp_size_t n;

			if (::getnameinfo (
				sa, len, XP_NULL, 0,
				mb_buf, xp_countof(mb_buf), NI_NUMERICSERV) != 0) return -1;
			n = xp_mcstowcs (mb_buf, buf, size);
			if (n == (xp_size_t)-1 || n == 0) return -1;
			return 0;
		#endif
		}

		int InetAddress::getHostName (xp_char_t* buf, xp_size_t size) const
		{
			xp_sckadr_t* sa = storage();
			xp_scklen_t len = storageSize();
			
		#if !defined(__linux) && !defined(__sun) && !defined(_WIN32)
			sa->sa_len = len;
		#endif

		#if defined(XP_CHAR_IS_MCHAR)
			return (::getnameinfo (sa, len,
				buf, size, XP_NULL, 0, 0) == 0)? 0: -1;
		#else
			xp_mchar_t mb_buf[MAX_HOST_LEN + 1];
			xp_size_t n;

			if (::getnameinfo (sa, len, 
				mb_buf, xp_countof(mb_buf), 
				XP_NULL, 0, 0) != 0) return -1;

			n = xp_mcstowcs (mb_buf, buf, size);
			if (n == (xp_size_t)-1 || n == 0) return -1;
			return 0;
		#endif
		}

		int InetAddress::getServiceName (xp_char_t* buf, xp_size_t size) const
		{
			xp_sckadr_t* sa = storage();
			xp_scklen_t len = storageSize();

		#if !defined(__linux) && !defined(__sun) && !defined(_WIN32)
			sa->sa_len = len;
		#endif

		#if defined(XP_CHAR_IS_MCHAR)
			// TODO: check the behavior of getnameinfo in other OSes
			return (::getnameinfo (sa, len,
				XP_NULL, 0, buf, size, 0) == 0)? 0: -1;
		#else
			xp_mchar_t mb_buf[MAX_SERVICE_LEN + 1];
			xp_size_t n;
			
			// NOTICE:
			//   getnameinfo in _WIN32 fails if the service name 
			//   can not be resolved when NI_NUMERICSERV is not specified.
			if (::getnameinfo (sa, len, XP_NULL, 0,
				mb_buf, xp_countof(mb_buf), 0) != 0) {
			#ifdef _WIN32
				if (::getnameinfo (sa, len,
					XP_NULL, 0, mb_buf, xp_countof(mb_buf), 
					NI_NUMERICSERV) != 0) return -1;
			#else
				return -1;
			#endif
			}

			n = xp_mcstowcs (mb_buf, buf, size);
			if (n == (xp_size_t)-1 || n == 0) return -1;
			return 0;
		#endif
		}

		String InetAddress::numericHost () const
		{
			xp_char_t buf[MAX_HOST_LEN + 1];
			if (getNumericHost (buf, xp_countof(buf)) == -1) return String();
			return String(buf);
		}	

		String InetAddress::numericService () const
		{
			xp_char_t buf[MAX_SERVICE_LEN + 1];
			if (getNumericService(buf, xp_countof(buf)) == -1) return String();
			return String(buf);
		}	

		String InetAddress::hostName () const
		{
			xp_char_t buf[MAX_HOST_LEN + 1];
			if (getHostName (buf, xp_countof(buf)) == -1) return String();
			return String(buf);
		}	

		String InetAddress::serviceName () const
		{
			xp_char_t buf[MAX_SERVICE_LEN + 1];
			if (getServiceName (buf, xp_countof(buf)) == -1) return String();
			return String(buf);
		}	

		int InetAddress::resolve (
			const xp_char_t* service, InetAddress* address) 
		{
			int errnum;
			return address->resolve_address (
				service, XP_NULL, 
				address->family(), address->type(), &errnum);
		}

		int InetAddress::resolve (
			const xp_char_t* service, const xp_char_t* host, 
			InetAddress* address) 
		{
			int errnum;
			return address->resolve_address (
				service, host, address->family(), address->type(), &errnum);
		}

		/* 
		 * NOTICE: 
		 *   When host is "", the address is resolved to localhost.
		 *   When host is XP_NULL, the address is resolved to INADDR_ANY 
		 *   or IN6ADDR_ANY_INIT depending on the address family.
		 */
		int InetAddress::resolve_address (
			const xp_char_t* service, const xp_char_t* host, 
			int family, int type, int* errnum)
		{
			struct addrinfo hints;
			struct addrinfo* info, * p;

			xp_assert (family == V0 || family == V4 || family == V6);
			xp_assert (errnum != XP_NULL);

			xp_memset (&hints, 0, xp_sizeof(hints));
			hints.ai_family = family;
			hints.ai_socktype = type;
			if (host == XP_NULL) hints.ai_flags = AI_PASSIVE; 
			else if (host[0] == XP_CHAR('\0')) host = XP_NULL;

		#if defined(XP_CHAR_IS_MCHAR)
			*errnum = ::getaddrinfo (host, service, &hints, &info);
		#else
			xp_mchar_t mb_host[MAX_HOST_LEN + 1];
			xp_mchar_t mb_service[MAX_SERVICE_LEN + 1];
			xp_mchar_t* p_host = XP_NULL;
			xp_mchar_t* p_service = XP_NULL;

			if (host != XP_NULL) {
				if (xp_wcstomcs (host, 
					mb_host, xp_countof(mb_host)) == 0) {
					*errnum = EAI_NONAME;
					return -1;
				}
				p_host = mb_host;
			}
			if (service != XP_NULL) {
				if (xp_wcstomcs (service, 
					mb_service, xp_countof(mb_service)) == 0) {
					*errnum = EAI_NONAME;
					return -1;
				}
				p_service = mb_service;
			}
			*errnum = ::getaddrinfo (p_host, p_service, &hints, &info);	
		#endif
			if (*errnum != 0) return -1;
			
			for (p = info; p != XP_NULL; p = p->ai_next) {
				if (family != V0 && p->ai_family != family) continue;
				if (type != 0 && p->ai_socktype != type) continue;
				if (xp_sizeof(address_storage) < p->ai_addrlen) continue;

				xp_memcpy (&address_storage, p->ai_addr, p->ai_addrlen);
				break;
			}
			::freeaddrinfo (info);
			return 0;
		}

	}
}
